package x7.heatctrl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalOutput;
import com.pi4j.io.gpio.Pin;
import com.pi4j.io.gpio.RaspiPin;
/**
 * Manages the Configuration
 * @author Simon
 *
 */
public class Config {

	// prefix
	private static final String SENSOR = "sensor.";
	
	/** singelton access to the Config-Object */
	public static Config INSTANCE = new Config();
	
	// List of rooms
	private ArrayList<Room> rooms;
	// mapping for Room to GPIO-Pin
	private Map<String,GpioPinDigitalOutput> pins=new HashMap<String, GpioPinDigitalOutput>();
	// the Weather-Checker
	private WeatherChecker weather;
	// Last-Status in case the Status is written to Disk
	private String lastStatus="";
	// the TimeZones
	private List<TimeZone> times;
	// time of the Last Update of the config-File
	private long lastUpdate;
	// path to the config-File
	private static File conf = new File("conf/heat.conf");

	/**
	 * Checks if the Config-File has changed and reloads it.
	 */
	public  void checkForUpdate() {
		if (lastUpdate==0 || (conf.exists() && conf.lastModified()!=lastUpdate))
		   update();
	}
	
	
	/**
	 * Reloads the Config-File to the System-Properties
	 */
	public  void update() {
	   if (conf.exists()) {
		   
		   // block all file access to the Config-File in order 
		   // to prevent race-conditions when writing the config
		   synchronized (INSTANCE) {
			   times=null;
			   InputStream in=null;
			   try {
	               in = new FileInputStream(conf);
	               // read config as Properties
				   Properties p = new Properties();
				   p.load(in);
				   
				   // set the trimmed values in System.properties
				   for (Map.Entry<Object,Object> entry:p.entrySet())
					   System.setProperty(entry.getKey().toString(), entry.getValue().toString().trim());
				   
				} catch (Exception e) {
					Errors.log("Error reading the Config", e);
				} finally {
					if (in!=null) try { in.close(); } catch (IOException e) {}
				}
  		   }
	   }

	   // update values
	   lastUpdate    = conf.lastModified();
	   
	   // set the timezone
	   String timezone = System.getProperty("current.timezone");
	   if (timezone!=null) {
		   java.util.TimeZone tz = java.util.TimeZone.getTimeZone(timezone);
		   if (tz!=null) java.util.TimeZone.setDefault(tz);
	   }
	}
	
	/**
	 * find the GPIO-Output for the given room or null if none is configured.
	 * @param room
	 * @return
	 */
	public  GpioPinDigitalOutput  getGPIOPin(String room) {
		GpioPinDigitalOutput found = pins.get(room);
		if (found!=null) return found;
		
		// get the pin from the config
		int pin= Integer.parseInt(System.getProperty("gpio."+room,"-1").trim());
		// nothing defined?
		if (pin<0) return null;
		
		try {
			found = GpioFactory.getInstance().provisionDigitalOutputPin(  
					(Pin) RaspiPin.class.getField("GPIO_"+(pin<10?"0":"")+pin).get(null));
		} catch (Exception e) {
			Errors.log("Error connecting to the gpio-pin "+pin+" for room "+room, e);
		}
		// cahce and return
		pins.put(room, found);
		return found;
	}
	
	
	/**
	 * return the targetTemperature for the given room
	 * @param room
	 * @return
	 */
	public  double getTargetTemp(String room) {
		
		// find the current Timezone first
		Date current = new Date();
		for (TimeZone tz: getTimeZones()) {
			if (!tz.fits(current)) continue;
			
			// get temperature with fallback to standard-target
			String val = System.getProperty("target." +tz.getPrefix() +room, System.getProperty("target."+room));
			if (val!=null) return Double.parseDouble(val);
		}
		
		// fallback, but should never happen, because the default-Timezone should always be found!
		return 20;
	}
	
	/**
	 * returns the List of defined Rooms
	 * @return
	 */
	public List<Room> getRooms() {
	   if (rooms==null) {
		    // first call or call we fill the List
		    rooms=new ArrayList<Room>();
		    for (Object key:System.getProperties().keySet()) {
		    	if (key.toString().startsWith(SENSOR)) 
					rooms.add(new Room(key.toString().substring(SENSOR.length())));
		    }
	   }
	   return rooms;
	}
	/** return the Interval in seconds to check the room-temperature */
	public int getCheckInterval() { return Integer.parseInt(System.getProperty("checkInterval", "30")); }
	
	/** return the Interval in seconds to log the room-temperature */
	public int getLogInterval()   { return Integer.parseInt(System.getProperty("logInterval"  ,"300")); }

	/** returns true if the log/status.ini should be written.*/
	public boolean isWriteStatus() {		return System.getProperty("writeStatus", "false").startsWith("t");	}

	/**
	 * this writes out a file called log/status.ini with the current temperature and settings
	 */
	public void writeStateLog() {
		StringBuilder sb = new StringBuilder();
		if (weather!=null) {
		  sb.append("[weather]\n");
		  sb.append("temperature = "+Math.round(weather.getCurrentTemperature()*100)/100f+"\n");
		  sb.append("pressure = "+(int)weather.getCurrentPressure()+"\n\n");
		}
		for (Room r:getRooms()) {
          int pin= Integer.parseInt(System.getProperty("gpio."+r.getId(),"-1"));

		  sb.append("["+r.getId()+"]\n");
		  sb.append("current = "+Math.round(r.getCurrentTemp()*10)/10f+"\n");
		  sb.append("target  = "+getTargetTemp(r.getId())+"\n");
		  sb.append("state   = "+((pin<0)?-1:(r.isHeating()?1:0)) +"\n");
		  sb.append("descr   = \""+System.getProperty("descr."+r.getId(),r.getId()) +"\"\n\n");
		}
		if (sb.toString().equals(lastStatus)) return;
		lastStatus=sb.toString();
		try {
			File f = new File("log/status.ini");
			if (f.exists()) {
				BufferedReader fr = new BufferedReader(new FileReader(f));
				StringBuilder eb = new StringBuilder();
				String line;
				while ((line= fr.readLine())!=null)
					eb.append(line).append("\n");
				fr.close();
				if (eb.toString().trim().equals(lastStatus.trim())) return;
			}
			FileWriter fw = new FileWriter(f);
			fw.write(lastStatus.trim());
			fw.close();
		} catch (Exception e) {
			Errors.log("Could not write the status", e);
		}
	}
	

	/**
	 * returns the WeatherChecker or null if no city is defined
	 * @return
	 */
	public WeatherChecker getWeather() {
		
		String city = System.getProperty("current.city");
		// is there a city defined?
		if (city==null || weather!=null) return weather;

		// define a WeatherCheck and set it
		return  weather = new WeatherChecker(city);
	}
	
	/** returns the Path to the File reading the Temperature */
	public File getSensor(String id) {
		return new File(System.getProperty(SENSOR+id,"").trim());
	}

	// the List of TimeZones as read in the config-File
	private List<TimeZone> getTimeZones() {
		if (times==null) {
			times=new ArrayList<TimeZone>();
		    for (Object key:System.getProperties().keySet()) {
		    	if (key.toString().startsWith("times.")) 
		    		times.add(new TimeZone(key.toString().substring(6), System.getProperty(key.toString())));
		    }
		    // add default-Timezone
		    times.add(new TimeZone("", null));
		}
		return times;
	}

	/** returns the Current TimeZone */
	public TimeZone getCurrentTimeZone() {
		Date current = new Date();
		for (TimeZone tz:getTimeZones()) {
			if (tz.fits(current)) return tz;
		}
		return null;
	}

	/**
	 * sets a new  TrargetTemperatur for a Room
	 * @param tz the Timezone
	 * @param roomId the room
	 * @param val the new Temperature
	 * @return
	 */
	public boolean setTargetTemp(TimeZone tz, String roomId,  double val) {
		if (tz==null) tz = getCurrentTimeZone();

		String tKey = "target."+tz.getPrefix()+roomId;
		
		// did the value change?
		if (System.getProperty(tKey, "0").equals(String.valueOf(val))) return false;
		
		// do we manage this room?
		if (System.getProperty(SENSOR+roomId)==null) return false;

		synchronized (INSTANCE) {
			
			BufferedReader in=null;
			FileWriter fw=null;
			StringBuilder sb = new StringBuilder();
			
			try {
				// read the config-file
				in = new BufferedReader(new FileReader(conf));
				
				boolean found = false;
				String line =null;
				while ((line=in.readLine())!=null) {
					int p = line.indexOf("=");
					if (p>0) {
						String key = line.substring(0,p).trim();
						// if it contains the key we are looking for, we just change it here
						if (key.equals(tKey)) {
							found=true;
							sb.append(line.substring(0,p+1)).append(" ").append(val).append("\n");
							continue;
						}
					}
					sb.append(line).append("\n");
				}
				
				// if not we add the key
				if (!found) 
					sb.append(tKey).append(" = ").append(val).append("\n");
				in.close();
				in=null;
				

				// write out the file-Content
				fw=new FileWriter(conf);
				fw.append(sb);
				fw.close();
				fw=null;
				
			} catch (IOException e) {
				Errors.log("Could not set the TargetTemperature for "+roomId+" to "+val, e);
				return false;
			} finally {
				
				// clean up in case of a error
			   if (in!=null) try { in.close(); } catch (IOException e) {}
			   if (fw!=null) try { fw.close(); } catch (IOException e) {}
			}
		}
		return true;
	}
	

}
